package com.sunny.excel.excel.uploader.impl;



import com.sunny.excel.dto.excel.ExcelColWithField;
import com.sunny.excel.dto.excel.ExcelMultiColWithField;
import com.sunny.excel.excel.uploader.ExcelUploader;
import com.sunny.excel.util.CollectionUtils;
import com.sunny.excel.util.ExcelUtils;
import com.sunny.excel.util.PrimitiveType;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * 开启excel扫描才 加入容器
 */

@Component
public class CommonExcelUploaderImpl implements ExcelUploader {
    private static final String[] TRUE_VALUES = {"Y", "T", "TRUE", "YES", "OK", "是"};

    protected String readCellAsString(DataFormatter dataFormatter, FormulaEvaluator formulaEvaluator, Cell cell) {
        formulaEvaluator.evaluate(cell);
        return dataFormatter.formatCellValue(cell, formulaEvaluator);
    }

    @Override
    public boolean canProcess(Class<?> dataClass) {
        return true;
    }

    @Override
    public <T> List<T> process(InputStream inputStream, int sheetIndex, Class<T> dataClass, String fileExt,
        String... contextParam) throws IOException {
        Map<String, Object> context = getContext(contextParam);
        Workbook workbook = createWorkBook(fileExt, inputStream);

        DataFormatter dataFormatter = new DataFormatter();
        FormulaEvaluator formulaEvaluator = workbook.getCreationHelper().createFormulaEvaluator();

        Sheet sheet = workbook.getSheetAt(sheetIndex);

        Iterator<Row> rowIterator = sheet.rowIterator();

        List<T> dataList = new ArrayList<>();
        Map<Integer, Field> fieldMap = new HashMap<>();
        Field excelRowNumField = ExcelUtils.getRowNumField(dataClass);
        boolean header = true;
        int rowNum = 0;
        try {
            while (rowIterator.hasNext()) {
                Row row = rowIterator.next();
                if (header) {
                    fieldMap = processHeader(row, dataClass, context);
                    header = false;
                    rowNum++;
                } else {
                    T data = processRow(dataFormatter, formulaEvaluator, row, fieldMap, dataClass);
                    if (data == null) {
                        continue;
                    }
                    dataList.add(data);

                    if (excelRowNumField != null) {
                        excelRowNumField.setInt(data, rowNum++);
                    }
                }
            }
            return dataList;
        } catch (IllegalArgumentException | IllegalAccessException ex) {
            throw new RuntimeException(" process excel inputStream error: " + ex.getMessage());
        }
    }

    protected Map<String, Object> getContext(String[] contextParam) {
        return null;
    }

    private <T> T processRow(DataFormatter dataFormatter, FormulaEvaluator formulaEvaluator, Row row,
        Map<Integer, Field> fieldMap, Class<T> dataClass) {
        try {
            T data = dataClass.newInstance();
            boolean emptyRow = true;
            for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) {
                Field field = fieldMap.get(i);
                Cell cell = row.getCell(i);

                if (field == null) {
                    continue;
                }

                if (cell == null || cell.getCellType() == Cell.CELL_TYPE_BLANK) {
                    setDynamicFieldValue(data, field, readCellAsString(dataFormatter, formulaEvaluator, cell));
                    continue;
                }
                emptyRow = false;
                setFieldValue(data, field, readCellAsString(dataFormatter, formulaEvaluator, cell));
            }
            if (emptyRow) {
                return null;
            }
            return data;
        } catch (Exception e) {
            //logger.error("failed to process data from excel, with fields {}", fieldMap, e);
            return null;
        }
    }

    private void setFieldValue(Object obj, Field field, String strValue) throws IllegalAccessException {
        field.setAccessible(true);
        Class<?> fieldClass = field.getType();

        if (fieldClass.isPrimitive()|| PrimitiveType.isWrap(fieldClass)) {
            setPrimitiveFieldValue(obj, field, strValue, fieldClass);
            return;
        }

        if (fieldClass == String.class) {
            field.set(obj, strValue);
            return;
        }

        if (setDynamicFieldValue(obj, field, strValue)) {
            return;
        }


        throw new IllegalArgumentException("only int, long, float, double, boolean and String are supported.");
    }

    private boolean setDynamicFieldValue(Object obj, Field field, String strValue) throws IllegalAccessException {
        field.setAccessible(true);
        Class<?> fieldClass = field.getType();

        if (fieldClass == List.class) {
            List list = (List) field.get(obj);
            if (CollectionUtils.isEmpty(list)) {
                list = new ArrayList();
            }
            list.add(strValue);
            field.set(obj, list);
            return true;
        }

        return false;
    }

    protected void setPrimitiveFieldValue(Object obj, Field field, String strValue, Class<?> fieldClass)
        throws IllegalAccessException {
        if (fieldClass == int.class || fieldClass == Integer.class) {
            field.setInt(obj, Integer.parseInt(strValue));
            return;
        }

        if (fieldClass == double.class || fieldClass == Double.class) {
            field.setDouble(obj, Double.parseDouble(strValue));
        }

        if (fieldClass == float.class || fieldClass == Float.class) {
            field.setFloat(obj, Float.parseFloat(strValue));
        }

        if (fieldClass == boolean.class || fieldClass == Boolean.class) {
            setBooleanField(obj, field, strValue);
        }

        if (fieldClass == long.class || fieldClass == Long.class) {
            field.set(obj, Long.parseLong(strValue));
        }
    }

    private void setBooleanField(Object obj, Field field, String strValue) throws IllegalAccessException {
        for (String trueValue : TRUE_VALUES) {
            if (trueValue.equalsIgnoreCase(strValue)) {
                field.setBoolean(obj, true);
                return;
            }
        }
        field.setBoolean(obj, false);
    }


    protected Map<Integer, Field> processHeader(Row header, Class<?> dataClass, Map<String, Object> context) {
        short firstCell = header.getFirstCellNum();
        if (firstCell < 0) {
            throw new RuntimeException("请检查输入参数 : 空文件");
        }

        Map<String, ExcelColWithField> colMap = ExcelUtils.readColAsMap(dataClass);
        Map<String, ExcelMultiColWithField> multiColMap = ExcelUtils.readMultiColAsMap(dataClass);

        Set<String> regexpKeyList = multiColMap.keySet();

        short lastCell = header.getLastCellNum();

        Map<Integer, Field> fieldsMap = new HashMap<>();
        for (int i = firstCell; i < lastCell; i++) {
            Cell cell = header.getCell(i);
            if (cell == null) {
                continue;
            }
            String h = cell.getStringCellValue();
            ExcelColWithField excelCol = colMap.get(h);
            if (excelCol != null) {
                colMap.remove(h);
                fieldsMap.put(i, excelCol.field);
                continue;
            }

            ExcelMultiColWithField excelMultiCol = null;
            for (String regexpKey : regexpKeyList) {
                Pattern pattern = Pattern.compile(regexpKey);
                Matcher matcher = pattern.matcher(h);
                boolean rs = matcher.matches();
                if (rs) {
                    excelMultiCol = multiColMap.get(regexpKey);
                    break;
                }
            }
            if (excelMultiCol == null) {
                continue;
            }

            fieldsMap.put(i, excelMultiCol.field);
            continue;
        }

        failIfMissingRequiredColumns(colMap);

        return fieldsMap;
    }

    private void failIfMissingRequiredColumns(Map<String, ExcelColWithField> colMap) {
        StringBuilder missingCols = new StringBuilder();
        for (ExcelColWithField col : colMap.values()) {
            if (col.excelCol.required()) {
                missingCols.append(col.header);
                missingCols.append(",");
            }
        }

        if (missingCols.length() > 0) {
            missingCols.setLength(missingCols.length() - 1);
            throw new RuntimeException("上传的文件缺少" + missingCols.toString());
        }
    }


    protected Workbook createWorkBook(String fileExt, InputStream inputStream) throws IOException {
        if (StringUtils.equals(fileExt, "xlsx")) {
            return new XSSFWorkbook(inputStream);
        } else {
            return new HSSFWorkbook(inputStream);
        }
    }

}
